
#include "system_command.h"
#include "utils.h"

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static bool fetch_line(struct bb_system_command*, char const**, size_t*);
static void shift_buffer_to_front(struct bb_system_command*);
static void read_from_pipe(struct bb_system_command*);

void bb_system_command_init(struct bb_system_command* sc, char const* command)
{
    size_t const command_len = strlen(command);
    if (command_len >= sizeof(sc->command))
        bb_fatal("system command exceeded allowed length");
    memcpy(sc->command, command, command_len);
    sc->pipe_fd = 0;
}

void bb_system_command_uninit(struct bb_system_command* sc)
{
    bb_system_command_finish(sc);
}

void bb_system_command_start(struct bb_system_command* sc)
{
    if (sc->pipe_fd)
        bb_fatal("attempted starting already-started system command");
    sc->pipe_fd = popen(sc->command, "r");
    if (!sc->pipe_fd)
        bb_fatal_errno("popen failed");
    sc->buffer_head = sc->buffer_tail = sc->buffer;
}

void bb_system_command_finish(struct bb_system_command* sc)
{
    if (!sc->pipe_fd)
        return;
    if (pclose(sc->pipe_fd) < 0)
        bb_fatal_errno("pclose failed");
    sc->pipe_fd = NULL;
}

bool bb_system_command_get_output_line(
        struct bb_system_command* sc, char const** ptr, size_t* len)
{
    if (!sc->pipe_fd)
        bb_fatal("attempted to get output from unstarted system command");
    while (sc->pipe_fd)
    {
        if (fetch_line(sc, ptr, len))
            break;
        shift_buffer_to_front(sc);
        read_from_pipe(sc);
    }
    return (sc->pipe_fd != 0);
}

static bool fetch_line(struct bb_system_command* sc, char const** ptr, size_t* len)
{
    *ptr = NULL;
    for(char* b = sc->buffer_head; b != sc->buffer_tail; ++b)
    {
        if (*b == '\n')
        {
            *b = '\0';
            *ptr = sc->buffer_head;
            *len = b - sc->buffer_head;
            sc->buffer_head = b + 1;
            return true;
        }
    }
    return false;
}

static void shift_buffer_to_front(struct bb_system_command* sc)
{
    if (sc->buffer_head == sc->buffer)
        return;
    else if (sc->buffer_head == sc->buffer_tail)
        sc->buffer_head = sc->buffer_tail = sc->buffer;
    else
    {
        size_t const len = sc->buffer_tail - sc->buffer_head;
        memmove(sc->buffer, sc->buffer_head, len);
        sc->buffer_head = sc->buffer;
        sc->buffer_tail = sc->buffer + len;
    }
}

static void read_from_pipe(struct bb_system_command* sc)
{
    static size_t const MAX_READ_SIZE = 32;
    size_t capacity = sc->buffer + sizeof(sc->buffer) - sc->buffer_tail;
    if (capacity > MAX_READ_SIZE)
        capacity = MAX_READ_SIZE;
    int read = fread(sc->buffer_tail, 1, capacity, sc->pipe_fd);
    if (read <= 0)
        bb_system_command_finish(sc);
    else
        sc->buffer_tail += read;
}

